package cldlock;

use English;
use Sys::Hostname;
use Data::Dumper;
use XML::Simple;

# Default class locks, if no method locks

$cldlock::last_error=undef;
$cldlock::last_rc=0;

%cldlock::lock_defs=();
$cldlock::lock_defs{NET}=["Network Config",30];
$cldlock::lock_defs{NBD_CLIENT}=["NBD Client",60];
$cldlock::lock_defs{NBD_SERVER}=["NBD Server",60];

#########################################################################
# The new method actually creates an empty list of locks,		#
# for all the defined locks above...					#
#########################################################################

sub new {
my $proto=shift;
my %args=@_;
my $class=ref($proto) || $proto;
my $self={};

	$self->{LOCK}={};
	if(exists($args{LOCKS})) {
		$self->{LOCKS}=$args{LOCKS};
	}
	if(! _reread($self)) {
		return undef;
	}

	bless($self,$class);
	return $self;
}

sub _reread {
my $self=shift;

	if(!exists($self->{LOCKS}) || ! -f $self->{LOCKS}) {
		$self->{DEFS}={};
		$self->{LOCK}={};
		::logmsg("No cllocks.xml - using builtin defaults.");
		for (keys %cldlock::lock_defs) {
			$self->{LOCK}->{$_}={
				DESC	=> $cldlock::lock_defs{$_}->[0],
				TIME	=> $cldlock::lock_defs{$_}->[1],
				CPROC	=> "",
				CPID	=> 0};
			$self->{DEFS}->{$_}={DESC => $cldlock::lock_defs{$_}->[0],
						TIME => $cldlock::lock_defs{$_}->[1]};
		}
		return(1);
	} else {
		::logmsg("Attempting to use data from cllocks.xml.");
		if(! -f $self->{LOCKS}) {
			$cldlock::last_error="Locks file not found.";
			$cldlock::last_rc=1;
			return 0;
		}
		my $f=$self->{LOCKS};
		eval '$xml=XMLin($f);';
		if($@) {
			$cldlock::last_error="Error parsing XML in file $f.";
			$cldlock::last_rc=2;
			return 0;
		}
		if(!exists($xml->{lock}) || ref($xml->{lock}) ne "HASH") {
			$cldlock::last_error="XML file $f contains invalid data.";
			$cldlock::last_rc=3;
			return 0;
		}
		my ($ch,$chash);
		$chash=$xml->{lock};

		# Scan each array element...					

		for $ch (keys(%$chash)) {
			if(!exists($chash->{$ch}->{desc}) || !exists($chash->{$ch}->{maxt})) {
				next;
			}
			$self->{DEFS}->{$ch}={DESC => $chash->{$ch}->{desc},
						TIME => $chash->{$ch}->{maxt}};
			$self->{LOCK}->{$ch}={
				DESC	=> $chash->{$ch}->{desc},
				TIME	=> $chash->{$ch}->{maxt},
				CPROC	=> "",
				CPID	=> 0};
		}
		::logmsg("Successfully loaded data from cllocks.xml.");
	}
	return 1;
}

#########################################################################
# Acquire a lock, using the following arguments:			#
# TYPE	 : Type of lock (must be known)					#
# [TIME] : Maximum number of seconds to hold lock for			#
# CPROC  : Program name holding the lock				#
# CPID   : Process ID requesting the lock.				#
#									#
# The routine will return ("OK","") or ("INUSE","USER"), or		#
# ("FAIL","MSG")							#
#########################################################################

sub acquire {
my $self=shift;
my %args=@_;
my ($cpid,$time,$cref,$cdef);
	
	if(!exists($args{TYPE}) || !exists($args{CPROC}) || !exists($args{CPID})) {
		return("FAIL","Missing mandatory argument.");
	}

	if(!exists($self->{LOCK}->{$args{TYPE}})) {
		return("FAIL","Unknown lock ".$args{TYPE});
	}
	$cdef=$self->{DEFS}->{$args{TYPE}};
	$cref=$self->{LOCK}->{$args{TYPE}};
	$cpid=$cref->{CPID};
	if(!exists($args{TIME}) || $args{TIME}>$cdef->{TIME}) {
		$time=$cdef->{TIME};
	} else {
		$time=$args{TIME};
	}
	if($cpid!=0) {
		#########################################################
		# Check the maximum time the current lock should be	#
		# held for and remove it if necessary.			#
		#########################################################
		if($cref->{TIME}+$cref->{STIME}>=time) {
			return("INUSE",$cref->{CPROC});
		}
	}
	$cref->{CPID}=$args{CPID};
	$cref->{CPROC}=$args{CPROC};
	$cref->{STIME}=time;
	$cref->{TIME}=$time;
	return("OK","");
}

#########################################################################
# Clear-out any old locks that are still being held beyonf the window	#
# necessary.								#
#########################################################################

sub clean {
my $self=shift;
my ($clock,$cdef);

	for (keys %cldlock::lock_defs) {
		$clock=$self->{LOCK}->{$_};
		$cdef=$self->{DEFS}->{$_};
		next if $clock->{CPID}==0;
		if($cdef->{TIME}+$clock->{STIME}<time) {
			delete $clock->{STIME};
			$clock->{CPID}=0;
		}
	}
}

#########################################################################
# This will release a lock - assuming that the specified process	#
# is still owning the lock. TYPE argument is required.			#
# The CPROC or CPID arguments can be used to identify it.		#
#########################################################################

sub release {
my $self=shift;
my %args=@_;

	if(!exists($args{TYPE})) {
		return("FAIL","Missing mandatory argument.");
	}
	if(!exists($args{CPROC}) && !exists($args{CPID})) {
		return("FAIL","Missing mandatory argument.");
	}

	if(!exists($self->{LOCK}->{$args{TYPE}})) {
		return("FAIL","Unknown lock ".$args{TYPE});
	}

	$cref=$self->{LOCK}->{$args{TYPE}};
	if(exists($args{CPROC})) {
		if($cref->{CPROC} eq $args{CPROC}) {
			$cref->{CPROC}="";
			$cref->{CPID}=0;
			return("OK","");
		} else {
			return("NOTOWNER",$cref->{CPROC});
		}
	}
	if($cref->{CPID} == $args{CPID}) {
		$cref->{CPROC}="";
		$cref->{CPID}=0;
		return("OK","");
	} else {
		return("NOTOWNER",$cref->{CPID});
	}
}

#########################################################################
# This routine will clear down any locks that have been in force longer	#
# then the maximum time for the type of lock.				#
#########################################################################

sub force_release {
my $self=shift;
my ($clock,$cdef);

	for (keys %cldlock::lock_defs) {
		$clock=$self->{LOCK}->{$_};
		$cdef=$self->{DEFS}->{$_};

		if($clock->{CPID}!=0) {
			if($clock->{STIME}+$clock->{TIME} > time) {
				$clock->{STIME}=0;
				$clock->{CPID}=0;
				$clock->{TIME}=$cdef->[1];
				$clock->{CPROC}="";
			}
		}
	}
}

#########################################################################
# Here is return the following information as a reference to a list:	#
# (TYPE,DESCRIPTION,[program],[pid],[start],[maxtime])			#
# Apart from the first two fields the remaining might be "" if the	#
# lock is not currently in use.						#
#########################################################################

sub list {
my $self=shift;
my $l;
my @rlist=();
my ($clock,$cdef);

	for (keys %cldlock::lock_defs) {
		$clock=$self->{LOCK}->{$_};
		$cdef=$self->{DEFS}->{$_};
		if($clock->{CPID}==0) {
			$l=[$_,$clock->{DESC},"","","",$cdef->{TIME}];
		} else {
			$l=[$_,$clock->{DESC},$clock->{CPROC},
				$clock->{CPID},$clock->{STIME},$cdef->{TIME}];

		}
		push @rlist,$l;
	}
	return \@rlist;
}

1;

